package main

import (
    "os"
    "fmt"
    "io/ioutil"
    "path/filepath"
    "encoding/binary"
    "io"
    "strings"
)

const BUFFER_SIZE = 10240

func main() {
    args := os.Args

    if len(args) == 1 {
        helpMode()
    } else if (args[1]) == "-h" {
        helpMode()
    } else if (args[1] == "-c") {
        combineMode()
    } else if (args[1] == "-s") {
        splitMode()
    } else {
        helpMode()
    }
}

func helpMode() {
    fmt.Println("Usage:")
    fmt.Println("\t for combine: filecombiner -c dest_file src_file1 src_file2, ... or filecombiner -c dest_file path")
    fmt.Println("\t for split: filecombiner -s file dest_path")
}

func combineMode() {
    length := len(os.Args)
    if length < 4 {
        fmt.Println("Error: not enough arguments.")
        return
    }

    destFile, err := os.Create(os.Args[2])
    if err != nil {
        fmt.Println(err.Error())
        return
    }

    defer destFile.Close()

    filePath := os.Args[3]
    if info, err := os.Stat(filePath); err == nil {
        if info.IsDir() {
            combineDir(destFile, filePath)
        } else {
            combineFiles(destFile, os.Args[3:])
        }
    } else {
        fmt.Println(err.Error())
        return
    }
}

func combineDir(destFile *os.File, filePath string) {
    files := make([]string, 0)
    if fileInfos, err := ioutil.ReadDir(filePath); err == nil {
        for _, f := range (fileInfos) {
            files = append(files, filepath.Join(filePath, f.Name()))
        }
        combineFiles(destFile, files)
    } else {
        fmt.Println(err.Error())
        return
    }
}

func combineFiles(destFile *os.File, files []string) {
    for _, f := range (files) {
        if strings.HasPrefix(filepath.Base(f), ".") {
            continue
        }
        if info, err := os.Stat(f); err == nil {
            if info.IsDir() {
                fmt.Println("Skip ", f, " because it is a directory.")
                continue
            } else {
                fmt.Println("processing ", f)
                if inFile, err := os.Open(f); err == nil {
                    defer inFile.Close()

                    buffer := make([]byte, 8)
                    filename := filepath.Base(f)
                    binary.LittleEndian.PutUint64(buffer, uint64(len(filename)))
                    destFile.Write(buffer)
                    destFile.Write([]byte(filename))

                    size := info.Size()
                    binary.LittleEndian.PutUint64(buffer, uint64(size))
                    destFile.Write(buffer)

                    readBuffer := make([]byte, BUFFER_SIZE)
                    for {
                        n, err := inFile.Read(readBuffer)
                        //fmt.Println(n, err, string(readBuffer))
                        //time.Sleep(time.Second)
                        if err == nil {
                            destFile.Write(readBuffer[0:n])
                        } else if err == io.EOF {
                            break
                        } else {
                            fmt.Println(err.Error())
                            os.Exit(1)
                        }
                    }
                } else {
                    fmt.Println(err.Error())
                    continue
                }
            }
        } else {
            fmt.Println(err.Error())
        }
    }
}

func splitMode() {
    length := len(os.Args)
    if length < 4 {
        fmt.Println("Error: not enough arguments.")
        return
    }
    filePath := os.Args[2]

    var f *os.File
    var err error

    if f, err = os.Open(filePath); err != nil {
        fmt.Println(err.Error())
        return
    }

    defer f.Close()


    destPath := os.Args[3]
    if info, err := os.Stat(destPath); err != nil {
        fmt.Println(err.Error())
        return
    } else if !info.IsDir() {
        fmt.Println("Error: ", destPath, " is not a directory.")
        return
    }

    buffer := make([]byte, BUFFER_SIZE)

    for {
        _, err := f.Read(buffer[0:8])
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        l := binary.LittleEndian.Uint64(buffer[0:8])
        fmt.Println("filename length is ", l)

        _, err = f.Read(buffer[0:l])
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        filename := string(buffer[0:l])
        fmt.Println("filename is ", filename)
        outFile, err := os.Create(filepath.Join(destPath, filename))
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        defer outFile.Close()

        _, err = f.Read(buffer[0:8])
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        l = binary.LittleEndian.Uint64(buffer[0:8])
        fmt.Println("file length is ", l)

        for ; l > 0; {
            k := l
            if k > BUFFER_SIZE {
                k = BUFFER_SIZE
            }
            _, err := f.Read(buffer[0:k])
            if err != nil {
                fmt.Println(err.Error())
                return
            }
            outFile.Write(buffer[0:k])
            l -= k
        }
    }
}
